﻿using System;
using System.Collections.Generic;
using System.Security.AccessControl;
using System.Security.Principal;
using System.Runtime.InteropServices;
using System.IO;

namespace Security2
{
    #region IdentityReference2
    public class IdentityReference2 : IDisposable
    {
        protected SecurityIdentifier sid;
        public string Sid
        {
            get { return sid.Value; }
        }

        protected NTAccount ntAccount;
        public string AccountName
        {
            get
            {
                if (ntAccount == null)
                    return string.Empty;
                else
                    return ntAccount.Value;
            }
        }

        private string lastError;
        public string LastError
        {
            get { return lastError; }
            set { lastError = value; }
        }

        #region Constructors
        public IdentityReference2(NTAccount ntAccount)
        {
            this.ntAccount = ntAccount;
            this.sid = (SecurityIdentifier)this.ntAccount.Translate(typeof(SecurityIdentifier));
        }

        public IdentityReference2(SecurityIdentifier sid)
        {
            this.sid = sid;
            try
            {
                this.ntAccount = (NTAccount)sid.Translate(typeof(NTAccount));
            }
            catch (Exception ex)
            {
                this.lastError = ex.Message;
            }
        }

        public IdentityReference2(IdentityReference ir)
        {
            NTAccount ntAccount = ir as NTAccount;
            SecurityIdentifier sid = ir as SecurityIdentifier;

            if (ntAccount != null)
            {
                this.ntAccount = ntAccount;
            }
            else if (sid != null)
            {
                this.sid = sid;
                try
                {
                    this.ntAccount = (NTAccount)sid.Translate(typeof(NTAccount));
                }
                catch (Exception ex)
                {
                    this.lastError = ex.Message;
                }
            }
            else
            {
                throw new ArgumentException("Cannot create IdentityReference2 with this IdentityReference");
            }
        }

        public IdentityReference2(string value)
        {
            var match = System.Text.RegularExpressions.Regex.Match(value, "(S-1-)[0-9-]+", System.Text.RegularExpressions.RegexOptions.IgnoreCase);
            string sid = match.Value;
            match = System.Text.RegularExpressions.Regex.Match(value, @"^[A-Z0-9-_ \.]+\\[A-Z0-9-_ \.]+", System.Text.RegularExpressions.RegexOptions.IgnoreCase);
            string name = match.Value;

            if (!string.IsNullOrEmpty(sid))
            {
                try
                {
                    this.sid = new SecurityIdentifier(sid);
                }
                catch (Exception ex)
                {

                    throw new InvalidCastException("Could not create an IdentityReference2 with the given SID", ex);
                }

                try
                {
                    this.ntAccount = (NTAccount)this.sid.Translate(typeof(NTAccount));
                }
                catch (Exception ex)
                {
                    this.lastError = ex.Message;
                }
            }
            else if (!string.IsNullOrEmpty(name))
            {
                try
                {
                    this.ntAccount = new NTAccount(name);
                    this.sid = (SecurityIdentifier)this.ntAccount.Translate(typeof(SecurityIdentifier));
                }
                catch (IdentityNotMappedException ex)
                {

                    throw ex;
                }
            }
            else
            {
                throw new InvalidCastException("The is neither a SID nor a valid account name (domain\\username)");
            }
        }
        #endregion

        #region conversion
        //NTAccount
        public static implicit operator NTAccount(IdentityReference2 ir2)
        {
            return ir2.ntAccount;
        }
        public static implicit operator IdentityReference2(NTAccount ntAccount)
        {
            return new IdentityReference2(ntAccount);
        }

        //SecurityIdentifier
        public static implicit operator SecurityIdentifier(IdentityReference2 ir2)
        {
            return ir2.sid;
        }
        public static implicit operator IdentityReference2(SecurityIdentifier sid)
        {
            return new IdentityReference2(sid);
        }

        //IdentityReference
        public static implicit operator IdentityReference(IdentityReference2 ir2)
        {
            return ir2.sid;
        }
        public static implicit operator IdentityReference2(IdentityReference ir)
        {
            NTAccount ntAccount = ir as NTAccount;
            SecurityIdentifier sid = ir as SecurityIdentifier;

            if (ntAccount != null) { return new IdentityReference2(ntAccount); }
            else if (sid != null) { return new IdentityReference2(sid); }
            else
            {
                throw new InvalidCastException("Error casting IdentityReference into IdentityReference2");
            }

        }

        //String
        public static implicit operator IdentityReference2(string value)
        {
            var match = System.Text.RegularExpressions.Regex.Match(value, "(S-1-)[0-9-]+", System.Text.RegularExpressions.RegexOptions.IgnoreCase);
            string sid = match.Value;
            match = System.Text.RegularExpressions.Regex.Match(value, @"^[A-Z0-9-\._ ]+\\[A-Z0-9-_ ]+", System.Text.RegularExpressions.RegexOptions.IgnoreCase);
            string name = match.Value;

            if (!string.IsNullOrEmpty(sid))
            {
                try
                {
                    return new IdentityReference2(new SecurityIdentifier(sid));
                }
                catch (Exception ex)
                {

                    throw new InvalidCastException("Could not create an IdentityReference2 with the given SID", ex);
                }
            }
            else if (!string.IsNullOrEmpty(name))
            {
                try
                {
                    return new IdentityReference2(new NTAccount(value));
                }
                catch (IdentityNotMappedException ex)
                {

                    throw ex;
                }
            }
            else
            {
                throw new InvalidCastException("The is neither a SID nor a valid account name (domain\\username)");
            }
        }
        public static implicit operator string(IdentityReference2 ir2)
        {
            return ir2.ToString();
        }

        //REQUIRED BECAUSE OF CONVERSION OPERATORS
        public override bool Equals(object obj)
        {
            bool result = false;

            SecurityIdentifier sid = obj as SecurityIdentifier;
            if (sid != null)
            {
                result = this.sid == sid;
            }

            NTAccount ntAccount = obj as NTAccount;
            if (ntAccount != null)
            {
                result = this.ntAccount == ntAccount;
            }

            IdentityReference2 id2 = obj as IdentityReference2;
            if (id2 != null)
            {
                result = this.sid == id2.sid;
            }

            string value = obj as string;
            if (value != null)
            {
                if (this.sid.Value == value)
                {
                    result = true;
                }
                if (this.ntAccount != null)
                {
                    if (this.ntAccount.Value.ToLower() == value.ToLower())
                    {
                        result = true;
                    }
                }
            }

            return result;
        }
        public override int GetHashCode()
        {
            return this.Sid.GetHashCode();
        }

        public override string ToString()
        {
            if (this.ntAccount == null)
            {
                return string.Format("{0}", this.sid.ToString());
            }
            else
            {
                return string.Format("{0} ({1})", this.ntAccount.ToString(), this.sid.ToString());
            }
        }
        #endregion

        #region comparison
        public static bool operator ==(IdentityReference2 a, IdentityReference2 b)
        {
            if (ReferenceEquals(a, null)) return ReferenceEquals(b, null);
            if (ReferenceEquals(b, null)) return false;
            return a.sid == b.sid;
        }
        public static bool operator !=(IdentityReference2 a, IdentityReference2 b)
        {
            if (ReferenceEquals(a, null)) return !ReferenceEquals(b, null);
            if (ReferenceEquals(b, null)) return true;
            return a.sid != b.sid;
        }
        //public static bool operator ==(IdentityReference2 id2, NTAccount nt)
        //{
        //    return id2.sid == nt;
        //}
        //public static bool operator !=(IdentityReference2 id2, NTAccount nt)
        //{
        //    return id2.sid != nt;
        //}
        //public static bool operator ==(IdentityReference2 id2, IdentityReference id)
        //{
        //    return (id2.sid == id as SecurityIdentifier) | (id2.ntAccount == id as NTAccount);
        //}
        //public static bool operator !=(IdentityReference2 id2, IdentityReference id)
        //{
        //    return (id2.sid != id as SecurityIdentifier) & (id2.ntAccount != id as NTAccount);
        //}
        //public static bool operator ==(IdentityReference2 id2, IdentityReference2 id)
        //{
        //    return id2.sid == id.sid;
        //}
        //public static bool operator !=(IdentityReference2 id2, IdentityReference2 id)
        //{
        //    return id2.sid != id.sid;
        //}
        //public static bool operator ==(IdentityReference2 id2, object obj)
        //{
        //    if (obj == null)
        //    {
        //        return false;
        //    }
        //    else
        //    {
        //        return (id2.sid == obj as SecurityIdentifier) |
        //           (id2.ntAccount == obj as NTAccount) |
        //           (id2 == obj as IdentityReference2) |
        //           (id2 == obj as IdentityReference);
        //    }
        //}
        //public static bool operator !=(IdentityReference2 id2, object  obj)
        //{
        //    throw new NotImplementedException();
        //}
        #endregion

        //private bool IsValidSid(string sid)
        //{
        //    string temp = null;
        //    if (Enum.IsDefined(typeof(System.Security.Principal.WellKnownSidType), sid))
        //    {
        //        return true;
        //    }
        //    else
        //    {
        //        System.Security.Principal.WellKnownSidType.
        //        System.Text.RegularExpressions

        //    }
        //}

        #region IDisposable Members
        public void Dispose()
        { }
        #endregion
    }
    #endregion

    #region EffectivePermissions
    public class EffectivePermissions
    {
        #region Win32
        private enum TRUSTEE_FORM
        {
            TRUSTEE_IS_SID,
            TRUSTEE_IS_NAME,
            TRUSTEE_BAD_FORM,
            TRUSTEE_IS_OBJECTS_AND_SID,
            TRUSTEE_IS_OBJECTS_AND_NAME
        }

        private enum TRUSTEE_TYPE
        {
            TRUSTEE_IS_UNKNOWN,
            TRUSTEE_IS_USER,
            TRUSTEE_IS_GROUP,
            TRUSTEE_IS_DOMAIN,
            TRUSTEE_IS_ALIAS,
            TRUSTEE_IS_WELL_KNOWN_GROUP,
            TRUSTEE_IS_DELETED,
            TRUSTEE_IS_INVALID,
            TRUSTEE_IS_COMPUTER
        }

        private enum MULTIPLE_TRUSTEE_OPERATION
        {
            NO_MULTIPLE_TRUSTEE,
            TRUSTEE_IS_IMPERSONATE
        }


        private struct TRUSTEE
        {
            public IntPtr pMultipleTrustee;
            public MULTIPLE_TRUSTEE_OPERATION MultipleTrusteeOperation;
            public TRUSTEE_FORM TrusteeForm;
            public TRUSTEE_TYPE TrusteeType;
            public IntPtr ptstrName;
        }

        [DllImport("advapi32.dll", SetLastError = true)]
        private static extern void BuildTrusteeWithSid(
            ref TRUSTEE pTrustee,
            byte[] sid
        );

        [DllImport("advapi32.dll")]
        private static extern uint GetEffectiveRightsFromAcl(byte[] pacl, ref TRUSTEE
        pTrustee, ref uint pAccessRights);
        #endregion

        private static uint GetEffectivePermissions(ObjectSecurity sd, IdentityReference2 identity, bool isDirectory)
        {
            uint access = 0;

            CommonSecurityDescriptor csd = new CommonSecurityDescriptor(false, isDirectory,
                                                                        sd.GetSecurityDescriptorSddlForm(
                                                                            AccessControlSections.All));

            DiscretionaryAcl dacl = csd.DiscretionaryAcl;

            byte[] daclBuffer = new byte[dacl.BinaryLength];
            dacl.GetBinaryForm(daclBuffer, 0);

            byte[] sidBuffer = new byte[((SecurityIdentifier)identity).BinaryLength];
            ((SecurityIdentifier)identity).GetBinaryForm(sidBuffer, 0);

            TRUSTEE t = new TRUSTEE();
            BuildTrusteeWithSid(ref t, sidBuffer);

            uint hr = GetEffectiveRightsFromAcl(daclBuffer, ref t, ref access);

            int i = Marshal.Release(t.ptstrName);

            return access;
        }

        public static uint GetEffectiveAccessMask(FileSystemInfo item, IdentityReference2 identity)
        {
            if (item as FileInfo != null)
            {
                return GetEffectivePermissions(((FileInfo)item).GetAccessControl(AccessControlSections.Access), identity, false);
            }
            else
            {
                return GetEffectivePermissions(((DirectoryInfo)item).GetAccessControl(AccessControlSections.Access), identity, true);
            }
        }

        public static FileSystemAccessRule2 GetEffectiveAccess(FileSystemInfo item, IdentityReference2 identity)
        {
            uint accessMask = 0;
            try
            {
                accessMask = GetEffectiveAccessMask(item, identity);
            }
            catch { }

            var ace = new FileSystemAccessRule((SecurityIdentifier)identity, (FileSystemRights)accessMask, AccessControlType.Allow);
            var ace2 = new FileSystemAccessRule2(ace, item);

            return ace2;
        }
    }
    #endregion
}